import time
from collections import deque
from subprocess import Popen, PIPE
from threading import Thread, Event


class Process(object):
    STATE_PENDING = "pending"
    STATE_RUNNING = "running"
    STATE_FINISHED = "finished"
    
    def __init__(self):
        self.status = self.STATE_PENDING
        self.proc = None
        self.pid = None
        self.retcode = None
        self.stdout = None
        self.stderr = None
    
    def __repr__(self):
        return "<Process %r (%s)>" % (self.executable, self.status)
    
    def start(self):
        assert self.status == self.STATE_PENDING
        self.proc = Popen(executable = self.executable, args = self.args, 
            cwd = self.workdir, env = self.env, close_fds = True, stdout = PIPE,
            stderr = PIPE, shell = False)
        self.pid = self.proc.pid
        self.status = self.STATE_RUNNING
    
    def wait(self):
        assert self.status == self.STATE_RUNNING
        self.proc.wait()
        self.retcode = self.proc.retcode
        self.stdout = self.proc.stdout.read()
        self.stderr = self.proc.stderr.read()
        self.proc = None
        self.status = self.STATE_FINISHED
    
    def poll(self):
        if self.status == self.STATE_RUNNING:
            if self.proc.poll():
                self.wait()
        return self.status


workers = set()
queue = deque()
new_input_event = Event()
queue_empty_event = Event()


class Worker(object):
    def __init__(self, name, host = None):
        assert host is None
        self.name = name
        self.host = host
        self.active = False
        self.thread = None
    
    def __repr__(self):
        return "Worker(%r, %r)" % (self.name, self.host)
    
    def start(self):
        """start worker"""
        assert not self.active
        self.active = True
        self.thread = Thread(target = self._worker)
        self.thread.start()
        self.resume()
    
    def suspend(self):
        """suspend this worker from accepting new jobs"""
        assert self.active
        if self in workers:
            workers.remove(self)
    
    def resume(self):
        """allow this worker to accept new jobs"""
        assert self.active
        workers.add(self)
    
    def stop(self):
        """finish current task and stop worker"""
        assert self.active
        self.suspend()
        self.active = False
        new_input_event.set()
        self.thread.join()
    
    def _worker(self):
        while self.active:
            if not queue:
                queue_empty_event.set()
                new_input_event.wait()
                new_input_event.clear()
            while queue:
                if not self.active:
                    return
                proc = queue.popleft()
                proc.start()
                proc.wait()


def add(proc):
    """adds a process to the workers' queue"""
    queue.add(proc)
    new_input_event.set()


def wait_all():
    """waits for the queue to be emptied, then stops all workers"""
    if not workers and queue:
        raise ValueError("no available workers to process queue")
    for worker in workers:
        worker.suspend()
    if queue:
        queue_empty_event.clear()
        queue_empty_event.wait()
    for worker in workers:
        worker.stop()















